package com.sunnada.demo.log;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import com.sunnada.demo.log.annotation.LogField;
import com.sunnada.demo.log.annotation.LogRecord;
import com.sunnada.demo.log.bean.TrackLog;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiOperation;
import org.aspectj.lang.JoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

/**
 * @program: mybatisproject2
 * @description: 操作日志工具
 * @author: 李瑞益
 * @create: 2018-10-28 14:26
 **/
public class TrackLogUtil {

    private final static Logger logger = LoggerFactory.getLogger(TrackLogUtil.class);

    private static TrackLogUtil trackLogUtil;

    private TrackLogUtil(){
    }

    /**
     * 比较两个对象的不同
     * @param newObject 实体1
     * @param oldObject 实体2
     * @return 比较后的信息
     */
    public static String compareEntity(Object newObject, Object oldObject) {
        Class<?> aClass1 = newObject.getClass();
        StringBuilder sb = new StringBuilder();
        Field[] publicFields1 = aClass1.getDeclaredFields();
        Object fieldValue1 = null;
        Object fieldValue2 = null;
        Field f2 = null;
        try {
            for(Field f : publicFields1){
                f.setAccessible(true);
                fieldValue1 = f.get(newObject);
                f2 = oldObject.getClass().getDeclaredField(f.getName());
                f2.setAccessible(true);
                fieldValue2 = f2.get(oldObject);

                //这里判断是否是主键
                LogField annotation = f.getAnnotation(LogField.class);
                //有注解说明是id，不做判断
                if(annotation != null && annotation.primaryKey()){
                    continue;
                }
                //获取注解信息
                String recordInfo = getFieldAnnotationValue(f2);
                //可匹配四种情况，分两大类情况处理；
                // 第一种情况：新对象为null，旧对象不为null
                if(fieldValue1 == null && fieldValue2 != null){
                    sb.append("字段[").append(recordInfo).append("]由：")
                            .append(fieldValue2.toString()).append(" 变更为：null；");
                }else if(fieldValue1 != null && !fieldValue1.equals(fieldValue2)){
                    sb.append("字段[").append(recordInfo).append("]由：")
                            .append(fieldValue2 == null ? "null" : fieldValue2.toString())
                            .append(" 变更为：").append(fieldValue1.toString()).append("；");
                }
            }
        } catch (IllegalAccessException | NoSuchFieldException e) {
            e.printStackTrace();
        }
        return sb.toString();
    }

    /**
     * 获取字段的注解信息；
     * 默认以logRecord获取，
     * 当没有logRecord时，以ApiModelProperty获取
     * 再没有就是以字段名为准
     * @param field 属性字段
     * @return 该字段的信息
     */
    public static String getFieldAnnotationValue(Field field){
        //增加注解信息;默认以logRecord为记录注解
        Annotation fieldAnnotation = ReflectUtil.getAnnotation(field,LogField.class);
        //如果没有注解信息，便以数据表字段名来记录
//        String recordInfo = "";
        if(fieldAnnotation != null){
            return ((LogField)fieldAnnotation).value();
        }else{
            //这里如果默认注解为null，那么获取该项目里的swagger注解
            fieldAnnotation = ReflectUtil.getAnnotation(field, ApiModelProperty.class);
            if (fieldAnnotation != null) {
                return ((ApiModelProperty)fieldAnnotation).value();
            }else{
                //如果这两个都没有就使用字段名作为
                return field.getName();
            }
        }
    }

    /**
     *  构建操作日志信息
     * @param operationTypeEnum 操作类型
     * @param joinPoint 切入对象
     * @param request 请求
     */
    public static TrackLog getTrackLog(OperationTypeEnum operationTypeEnum, JoinPoint joinPoint, HttpServletRequest request){
        TrackLog log = new TrackLog();
        //切入点名，一般为方法名
        String pointName = joinPoint.getSignature().getName();
        //类全名
        String classDiplayName = joinPoint.getSignature().getDeclaringTypeName();
        //mongodb的时间飞北京时间，存入到数据库后时间会比当前时间慢8个小时
        log.setCreateTime(new Date(System.currentTimeMillis()+8*3600*1000));
//        log.setClassName();//类名
        log.setClasDisplayName(classDiplayName);
        //添加用户信息
        Map<String, String> userInfo =  getUserInfo();
        log.setUserId(userInfo.get("userId"));
        log.setUserName(userInfo.get("userName"));
        //请求中的参数
        Map<String, String> parameters = getParameters(request);
        StringBuilder sb = new StringBuilder();
        //获取controller的方法的注解信息
        Annotation methodAnnotation = ReflectUtil.getMethodAnnotaionByClassName(classDiplayName, pointName, LogRecord.class);
        //todo 这里不够灵活
        String methodInfo = "";
        if (methodAnnotation != null) {
            methodInfo = ((LogRecord) methodAnnotation).value();
        }else{
            //当日志注解没有时，获取swagger注解信息
            methodAnnotation = ReflectUtil.getMethodAnnotaionByClassName(classDiplayName,pointName, ApiOperation.class);
            if(methodAnnotation != null) {
                methodInfo = ((ApiOperation) methodAnnotation).value();
            }
        }

        //操作信息设置
        switch (operationTypeEnum){
            case LOGIN:
//                sb.append("登录操作；");
                sb.append(methodInfo).append("；");
                sb.append("[");
                for(Map.Entry<String,String> entry: parameters.entrySet()){
                    sb.append(entry.getKey()).append("=");
                    sb.append(entry.getValue()).append("；");
                }
                sb.append("]");
                break;
            case ADD:
//                sb.append("新增操作；");
                sb.append(methodInfo);
                getAddInfo(joinPoint,sb,log);
                break;
            case UPDATE:
//                sb.append("更新操作；");
                sb.append(methodInfo);
                getUpdateInfo(joinPoint,sb,log);
                break;
            case DELETE:
//                sb.append("删除操作；");
                sb.append(methodInfo);
                getDeleteInfo(classDiplayName,parameters,sb,log);
                break;
                default:
                    break;
        }


        //操作信息
        log.setLogMessage(sb.toString());
        //操作类型记录：新增、更新、删除
        log.setOperationType(operationTypeEnum.getIndex()+"");
        //请求的url
        log.setUrl(request.getRequestURL().toString());
        return log;
    }

    /**
     * 获取类上的注解信息
     * @param object 实体对象
     * @return 类的注解信息（可能为null）
     */
    public static String getClassAnnotationValue(Object object) {
        Class<?> aClass = object.getClass();
        ApiModel annotation = aClass.getAnnotation(ApiModel.class);
        if(annotation != null) {
            return annotation.value();
        }
        return null;
    }

    /**
     * 构建新增操作信息
     * @param joinPoint 切入对象
     * @param sb 信息
     */
    public static void getAddInfo(JoinPoint joinPoint,StringBuilder sb,TrackLog log){
        Object[] args = joinPoint.getArgs();
        for(Object obj : args){
            if (StringUtils.isEmpty(obj)) {
                continue;
            }
            //判断是否是基本类型
            if(ReflectUtil.isBaseDefault(obj)) {
                logger.info("对象非实体；"+obj.getClass().getName()+"："+obj);
                continue ;
            }
            sb.append("[");
            if (obj instanceof MultipartFile) {
                sb.append("上传的文件类型：").append(((MultipartFile) obj).getContentType())
                        .append("，文件名：").append(((MultipartFile) obj).getOriginalFilename());
                sb.append("，文件大小：").append(((MultipartFile) obj).getSize() / 1024.0).append("KB");
            }else {
                setSimpleInfo(obj, sb);
            }
            sb.append("]");
            //设置实体名称
            log.setEntityName(getClassAnnotationValue(obj));
//            log.setClassName(obj.getClass().getName());
            System.out.println(sb.toString());

        }
    }

    /**
     * 更新操作构建信息
     * @param joinPoint 切入对象
     * @param sb 信息
     */
    public static void getUpdateInfo(JoinPoint joinPoint,StringBuilder sb,TrackLog log){
        //更新操作获取JoinPoint里的对象
        Object[] args = joinPoint.getArgs();
        for(Object obj : args){
            sb.append(compareEntityToData(obj));
            log.setEntityName(getClassAnnotationValue(obj));
//            log.setClassName(obj.getClass().getName());
        }
    }

    /**
     * 构建删除操作信息
     * @param classDiplayName 全类名
     * @param parameters 参数键值
     * @param sb 信息
     */
    public static  void getDeleteInfo(String classDiplayName, Map<String, String> parameters,StringBuilder sb,TrackLog log){
        //删除操作只有 id,所以要获取数据库的数据
        String className = ReflectUtil.getSimpleClassName(classDiplayName);
        Object dao = DataReflectUtil.getDaoByClassName(className, "Dao");
        //如果没有，可能是使用JPA
        if(dao == null){
            dao = DataReflectUtil.getDaoByClassName(className, "Repository");
        }
        //可能使用MyBatis
        if(dao == null){
            dao = DataReflectUtil.getDaoByClassName(className, "Mapper");
        }
        //TODO 这里的dao可能为空
        //这里参数类型为int
        try {
            Object dataObj = DataReflectUtil.getDataById(dao,parameters.get("id"));
            sb.append("[");
            setSimpleInfo(dataObj,sb);
            sb.append("]");
            log.setEntityName(getClassAnnotationValue(dataObj));
//            log.setClassName(dataObj.getClass().getName());
        } catch (NumberFormatException e) {
            e.printStackTrace();
        }
    }


    /**
     * 设置实体的字段信息
     * @param obj  实体
     * @param sb 信息存储对象
     */
    public static void setSimpleInfo(Object obj,StringBuilder sb){
        if (obj == null) {
            return;
        }
        Class<?> aClass1 = obj.getClass();
        Field[] publicFields1 = aClass1.getDeclaredFields();
        Object fieldValue1 = null;
        try {
            for(Field f : publicFields1) {
                f.setAccessible(true);
                fieldValue1 = f.get(obj);

                //这里判断是否是主键
                LogField annotation = f.getAnnotation(LogField.class);
                //有注解说明是id，不做判断
                if (annotation != null && annotation.primaryKey()) {
                    System.out.println(aClass1.getSimpleName()+"的主键："+f.get(obj));
                    continue;
                }
                //或者没有打注解的id
                if ("id".equalsIgnoreCase(f.getName())) {
                    System.out.println(aClass1.getSimpleName()+"的主键："+f.get(obj));
                    continue;
                }
                //获取注解信息
                String recordInfo = getFieldAnnotationValue(f);
                sb.append(recordInfo).append("=").append(fieldValue1).append("；");
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
    /**
     * 获取实体对象中属性字段的注解信息和值
     * @param object 实体对象
     * @param logRecord 日志注解类
     */
    public static Map<Object,Object> getEntityAnnotationInfoAndFieldValue(Object object,Class logRecord){
        Class<?> aClass = object.getClass();
        Map<Object,Object> map = new HashMap<>();

        Field[] fields = aClass.getDeclaredFields();
        try {
            for(Field  f: fields){
                f.setAccessible(true);
                //获取注解
                Annotation annotation = f.getAnnotation(logRecord);
                if(annotation != null){
                    map.put(annotation,f.get(object));
                }else{
                    map.put(f.getName(),f.get(object));
                }
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return map;
    }


    /**
     * 将传入的不知名对象和数据库中的数据比较
     * @param obj1 数据对象
     */
    public static String compareEntityToData(Object obj1) {
        if(ReflectUtil.isBaseDefault(obj1)) {
            logger.info("对象非实体");
            return "";
        }
//        Map<String, Object> obj1Field = getFieldAndValueFromEntity(obj1);
        //该项目里使用的用户登录是UserManageController
        //所以，
        Object dao = DataReflectUtil.getDaoByObj(obj1, "Dao");
        //如果没有，可能是使用JPA
        if(dao == null){
            dao = DataReflectUtil.getDaoByObj(obj1,"Repository");
        }
        //可能使用MyBatis
        if(dao == null){
            dao = DataReflectUtil.getDaoByObj(obj1,"Mapper");
        }
//todo 可能会包空指针异常；dao为空
        Object dataObj = DataReflectUtil.getDataById(dao,ReflectUtil.getId(obj1));
        String info = null;
        if (dataObj != null) {
            info = compareEntity(obj1, dataObj);
        }
        return info;
    }

    /**
     * 传入要更新的对象和旧版本对象构建操作日志
     * 手动输入两个对象，
     * @param newObj 已更新的对象信息
     * @param oldbj 未更新的对象信息
     * @return
     */
//    public static TrackLog getUpdateTrackLog(Object newObj, Object oldbj){
//        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
//        HttpServletRequest request = attributes.getRequest();
//        TrackLog trackLog = new TrackLog();
//        //设置用户信息
//        Map<String,String> userMap = getUserInfo();
//        trackLog.setUserName(userMap.get("userName"));
//        trackLog.setUserId(userMap.get("userId"));
//        //更改的类 de 名称
//        trackLog.setClassName(newObj.getClass().getSimpleName());
//        trackLog.setClassName(newObj.getClass().getName());//类全名
//        //更改的信息
//        trackLog.setLogMessage(compareEntity(newObj,oldbj));
//        //时间
//        trackLog.setCreateTime(new Date(System.currentTimeMillis()+8*3600*1000));
//        //类型
//        trackLog.setOperationType(OperationTypeEnum.UPDATE.getIndex()+"");
//        //url
//        trackLog.setUrl(request.getRequestURL().toString());
//        System.out.println(trackLog.toString());
//        return trackLog;
//    }

    /**
     * 获取用户信息
     * @return
     */
    public static Map<String, String> getUserInfo(){
        String userName = "none";
        String userId = "0";
        Map<String,String> userMap = new HashMap<>();
        //shiro
//        Subject subject = SecurityUtils.getSubject();
//        User user = (User)subject.getPrincipal();
//        if(user != null){
//            userName = user.getUserName();
//            userId = user.getUserId().toString();
//        }else{
            //当shiro框架里没有用户信息时，查看session
            //session
            HttpServletRequest request = getHttpServletRequest();
            HttpSession session = request.getSession();
            userName = session.getAttribute("userId") == null ? userName:session.getAttribute("userId").toString();
            userId = session.getAttribute("userName") == null ? userId:session.getAttribute("userName").toString();
//        }
        userMap.put("userName",userName);
        userMap.put("userId",userId);
        return userMap;
    }

    /**
     * 系统日志记录
     * 记录类型：info、debug、warn、error
     * 信息记录包括：userName（用户）、userId（用户id）、message（异常信息）
     *
     * @param mes 信息对象
     * @return 自定义信息
     */
    public static void addSystemLog(Object mes){
        HttpServletRequest request = getHttpServletRequest();
        //用户对象
        if(request.getRequestURI().toLowerCase().contains("login")) {
            return ;
        }
        Map<String, String> userInfo = getUserInfo();
        MDC.put("userName",userInfo.get("userName"));
        MDC.put("userId",userInfo.get("userName"));
        //添加请求的url
        MDC.put("url",request.getRequestURL().toString());
        //异常信息
        if(mes instanceof Throwable){
            logger.info(getExceptionMessage((Throwable)mes).toString());
        }
        else{
            logger.info(mes.toString());
        }
    }

    /**
     * 获取请求
     * @return 请求对象
     */
    public static HttpServletRequest getHttpServletRequest() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        Assert.notNull(attributes,"获取请求信息失败！");
        return attributes.getRequest();
    }

    /**
     * 异常信息获取
     * @param mes 异常对象
     * @return 异常信息
     */
    public static StringBuilder getExceptionMessage(Throwable mes){
        StringBuilder message = new StringBuilder();
        message.append("异常信息：");
        message.append(((Throwable) mes).getMessage());
        message.append("错误类型：");
        message.append(((Throwable) mes).getClass().getName());
        message.append("跟踪：");
        message.append(Arrays.toString(((Throwable) mes).getStackTrace()));
        return message;
    }


    /**
     * 获取请求中的参数
     * @param request 获取请求中的参数
     * @return
     */
    public static Map<String,String> getParameters(HttpServletRequest request){
        //获取请求中的参数
        Enumeration<String> enumeration = request.getParameterNames();
        Map<String,String> parameterMap = new HashMap<String,String>();
        while (enumeration.hasMoreElements()){
            String parameter = enumeration.nextElement();
            parameterMap.put(parameter,request.getParameter(parameter));
        }
        return parameterMap;
    }


}
